use std::ffi::{CStr, CString};

use libc::c_void;

use crate::{ffi::*, core::{Value, GetterMap, LuaEngine, LuaNumber, SetterMap}};

pub unsafe fn get_index_string(state: *mut lua_State) -> String {
    // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | VALUE |                           PARAMS FROM LUA
    let index_string = lua_tolstring(state, 2, std::ptr::null_mut());
    let index_string = String::from_utf8(CStr::from_ptr(index_string).to_bytes().to_vec()).unwrap();
    index_string
}

pub unsafe fn get_object_address(state: *mut lua_State) -> i64 {
    let object_address;
    match lua_type(state, 1) {
        2 => {
            object_address = lua_touserdata(state, 1) as usize as i64;
        },
        5 => {
            let c_string = CString::new("Object").expect("");

            lua_pushstring(state, c_string.as_ptr());
            // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | Object |                       SET ACCESS TABLE INDEX_STR
            lua_gettable(state, 1);
            // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | TABLE.Object |                 FIND TABLE.__ObjectAddress
            object_address = lua_tointegerx(state, -1, std::ptr::null_mut());
            // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | TABLE.Object |                 GET VALUE TABLE.__ObjectAddress
            lua_settop(state, -2);
            // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR |                                POP VALUE TABLE.__ObjectAddress
        },
        _ => unreachable!()
    }
    object_address
}

pub unsafe fn get_struct_name(state: *mut lua_State) -> String {
    // Get Metatable
    lua_getmetatable(state, 1);
    // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | TABLE.METATABLE |                          POP VALUE TABLE.__ObjectAddress

    let c_string = CString::new("__name").expect("");
    lua_pushstring(state, c_string.as_ptr());
    // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | TABLE.METATABLE | "__name" |               SET ACCESS TABLE __name
    lua_rawget(state, -2);
    // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | TABLE.METATABLE | METATABLE.__name |       FIND TABLE.__name
    let struct_name = lua_tolstring(state, -1, std::ptr::null_mut());
    let struct_name = String::from_utf8(CStr::from_ptr(struct_name).to_bytes().to_vec()).unwrap();
    // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | TABLE.METATABLE | TABLE.__name |           GET VALUE TABLE.__name
    lua_settop(state, -3);
    // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR |                                            POP VALUE TABLE.__name

    struct_name
}

pub unsafe fn set_value(state: *mut lua_State, object_address: i64, struct_name: &String, index_string: &String, value: &Value) {
    // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | VALUE |                           PARAMS FROM LUA
    
    // Get function
    let c_string = CString::new("setter_map").expect("");
    lua_getglobal(state, c_string.as_ptr());
    // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | VALUE | setter_map |                               POP VALUE TABLE.__name

    // Set Value
    let setter_map_address = &*(lua_touserdata(state, -1) as usize as *mut libc::c_void as *mut SetterMap);
    let setter = setter_map_address.get(struct_name);

    // Clear
    lua_settop(state, -2);
    // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR |                               POP VALUE TABLE.__name

    if setter.is_none() {
        return
    }
    setter.unwrap()(object_address as *const c_void, &index_string, &value);
}

pub unsafe fn get_value_from_stack(state: *mut lua_State) -> Value {
    let mut value = Value::Nil();
    // println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | VALUE |                           PARAMS FROM LUA

    match lua_type(state, -1) {
        0 => {
            value = Value::Nil();
            // output = CString::new("nil").unwrap();
            // type_str = String::from("nil            ");
        },
        1 => {
            // output = CString::new(lua_toboolean(self.state, i).to_string()).unwrap();
            // type_str = String::from("boolean        ");
        },
        2 => {
            // output = CString::new((lua_touserdata(self.state, i) as usize).to_string()).unwrap();
            // type_str = String::from("lightuserdata  ");
        },
        3 => {
            let is_integer = lua_isinteger(state, -1) != 0;
            if is_integer {
                value = Value::Number(LuaNumber::Integer{ 0: lua_tointegerx(state, -1, std::ptr::null_mut()) });
            } else {
                value = Value::Number(LuaNumber::Float{ 0: lua_tonumberx(state, -1, std::ptr::null_mut()) });
            }
            // type_str = String::from("number         ");
        },
        4 => {
            // output = CStr::from_ptr(lua_tolstring(self.state, i, std::ptr::null_mut())).to_owned();
            // type_str = String::from("string         ");
        },
        5 => {
            // output = CString::new("todo").unwrap();
            // type_str = String::from("table          ");
        },
        6 => {
            // output = CString::new("todo").unwrap();
            // type_str = String::from("function       ");
        },
        7 => {
            // output = CString::new((lua_touserdata(self.state, i) as usize).to_string()).unwrap();
            // type_str = String::from("userdata       ");
        },
        8 => {
            // output = CString::new("todo").unwrap();
            // type_str = String::from("thread         ");
        },
        _ => {
            unreachable!()
            // output = CString::new("unknown").unwrap();
            // type_str = String::from("unknown        ");
        }
    };

    value
}

pub unsafe fn set_newvalue_to_table(state: *mut lua_State) {
    lua_pushvalue(state, -2);
    println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | new value | INDEX_STR |                           SET TABLE.METATABLE = MyMetatable
    lua_pushvalue(state, -2);
    println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | new value | INDEX_STR | new value |                           SET TABLE.METATABLE = MyMetatable
    lua_rawset(state, 1);
    println!("Stack Num: {}", lua_gettop(state));         //  |·····| TABLE | INDEX_STR | new value |                           SET TABLE.METATABLE = MyMetatable
}